package com.wx.spring.utils;

import com.wx.spring.annotations.*;
import com.wx.spring.ioc.IocContainer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

import static com.wx.spring.constant.ResourcesSuffix.CLASS_RESOURCES;

/**
 * @author wx
 * @Description: 扫描工具类(核心方法是getClassName和getComponentList)
 * @date 2019/1/5 000519:38
 */
@Slf4j
public class ScanUtil {


    /**
     * 类容器
     */
    private static List<String> listClassName = new ArrayList<>();


    private ScanUtil() {
        throw new RuntimeException("工具类禁止实例化");
    }

    /**
     * @param packageName 包名
     * @description: 扫描指定包下面的所有类名
     * @author: wx
     * @date: 2019/1/5 0005
     * @return: java.util.List<java.lang.String>
     */
    public static List<String> getClassName(String packageName) {
        Enumeration<URL> urls;
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        String newPackageName = packageName.replace(".", "/");
        try {
            urls = contextClassLoader.getResources(newPackageName);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                File packageFile = new File(url.getPath());
                File[] files = packageFile.listFiles();
                if (files == null) {
                    break;
                }
                for (File file : files) {
                    //如果是class，则添加到list中返回
                    if (file.getName().endsWith(CLASS_RESOURCES)) {
                        String templeName = (packageName.replace("/", ".") + "." + file.getName());
                        String newTempleName = templeName.substring(0, templeName.lastIndexOf("."));
                        listClassName.add(newTempleName);
                    }
                    //如果是package，则继续递归遍历
                    else {
                        String nextPackageName = newPackageName + "." + file.getName();
                        getClassName(nextPackageName);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return listClassName;
    }


    /**
     * @description: 根据包路径初始化-待实例化class容器
     * @author: wx
     * @date: 2019/1/11 0011
     * @param packageName
     * @return: void
     */
    public static void initInstanceSet(String packageName) throws ClassNotFoundException {
        //获取所有类
        List<String> classNameList = getClassName(packageName);
        //将扫描的接口和其实现类，使用map对应上
        makeInterfaceAndImplMap(classNameList);
        for (String className : classNameList) {
            //添加元素
            addAnnotationsInstanceSet(className);
        }


    }

    /**
     * @param className
     * @description: 添加带有注解的class-存储待实例化的对象的class容器
     * @author: wx
     * @date: 2019/1/8 0008
     * @return: void
     */
    public static void addAnnotationsInstanceSet(String className) throws ClassNotFoundException {
        Class<?> aClass = Class.forName(className);
        //在此处添加要识别的注解，也是每次扫描的顺序，最好遵循习惯
        addNewAnnotation(MyService.class, aClass);
        addNewAnnotation(MyComponent.class, aClass);
        addNewAnnotation(MyRepository.class, aClass);
        addNewAnnotation(MyController.class, aClass);
    }

    /**
     * @param annotationClass
     * @param aClass
     * @description: 设置指定注解的类
     * @author: wx
     * @date: 2019/1/9 0009
     * @return: void
     */
    public static <A extends Annotation> void addNewAnnotation(Class<A> annotationClass, Class<?> aClass) throws ClassNotFoundException {
        //如果类上有注解
        if (!aClass.isInterface() && AnnotationUtils.isNotEmpty(aClass.getAnnotation(annotationClass))) {
            //获取类下的所有属性
            Field[] fields = aClass.getDeclaredFields();
            //如果没有任何属性
            if (VesselUtils.isArrayEmpty(fields)) {
                IocContainer.getInstanceSet().add(aClass.getName());
                return;
            }

            //如果所有属性没有@MyAutowired注解
            if (!isEmptyAutowired(fields)) {
                IocContainer.getInstanceSet().add(aClass.getName());
                return;
            }
            //如果属性上有@MyAutowired
            setTheImplementationOfAnnotationCorrespondingProperties(fields,MyAutowired.class);
            IocContainer.getInstanceSet().add(aClass.getName());
        }
        //如果是需要动态的代理注入的接口，加入到实例化的链表中
        else if (aClass.isInterface()  && AnnotationUtils.isNotEmpty(aClass.getAnnotation(annotationClass))) {
            Set<String> implSet = IocContainer.getInterfaceAndImplMap().get(aClass.getName());
            if (CollectionUtils.isNotEmpty(implSet)){
                if (implSet.contains("proxy")){
                    IocContainer.getInstanceSet().add(aClass.getName());
                }else {
                    for (String implClass : implSet) {
                        //如果实现类不存在实例化类容器，则递归调用addAnnotationsInstanceSet
                        if (!IocContainer.getInstanceSet().contains(implClass)) {
                            addAnnotationsInstanceSet(implClass);
                        }
                    }
                }
            }
        }
    }

    /**
     * @param fields
     * @description: 判断一组属性里面有没有@MyAutowired注解
     * @author: wx
     * @date: 2019/1/5 0005
     * @return: boolean
     */
    private static boolean isEmptyAutowired(Field[] fields) {
        for (Field field : fields) {
            if (AnnotationUtils.isNotEmpty(field.getAnnotation(MyAutowired.class))) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param classNameList
     * @description: 组装接口和实现类
     * @author: wx
     * @date: 2019/1/5 0005
     * @return:
     */
    private static void makeInterfaceAndImplMap(List<String> classNameList) throws ClassNotFoundException {
        Class<?> aClass;
        //interfaceNameSet保存接口类
        Set<String> interfaceNameSet = new HashSet<>();
        //interfaceExistSet保持被实现的接口类
        Set<String> interfaceExistSet = new HashSet<>();
        //循环类名，将接口类名注入到集合中
        for (String className : classNameList) {
            if (MyStringUtils.isBlank(className)) {
                continue;
            }
            aClass = Class.forName(className);
            if (aClass.isInterface()) {
                interfaceNameSet.add(aClass.getName());
            }
        }
        for (String className : classNameList) {
            //获取此类所实现的接口与父类
            Class<?>[] interfaces = Class.forName(className).getInterfaces();
            for (Class<?> interfaceClass : interfaces) {
                if (interfaceNameSet.contains(interfaceClass.getName())) {
                    //如果有接口或则父类，则组成map
                    Set<String> implClassSet = IocContainer.getInterfaceAndImplMap().get(interfaceClass.getName());
                    if (CollectionUtils.isEmpty(implClassSet)) {
                        implClassSet = new HashSet<>();
                    }
                    implClassSet.add(className);
                    IocContainer.getInterfaceAndImplMap().put(interfaceClass.getName(), implClassSet);
                    interfaceExistSet.add(interfaceClass.getName());
                }
            }


        }
        //interfaceNameSet接口类集合 - 被实现的接口集合 = 未被实现的接口集合  (差集)
        interfaceNameSet.removeAll(interfaceExistSet);
        //需要动态代理注入的接口，在map中用value = proxy来识别
        if (CollectionUtils.isNotEmpty(interfaceNameSet)) {
            for (String spareInterfaceName : interfaceNameSet) {
                Set<String> implClassSet = new HashSet<>();
                implClassSet.add("proxy");
                IocContainer.getInterfaceAndImplMap().put(spareInterfaceName, implClassSet);
            }
        }
    }


    /**
     * @description: 接口转换为实现类
     * @author: wx
     * @date: 2019/1/11 0011
     * @param newFileName
     * @return: java.util.Set<java.lang.String>
     */
    private static Set<String> convertInterfaceToImpl(String newFileName) {
        Set<String> implClassSet = IocContainer.getInterfaceAndImplMap().get(newFileName);
        if (CollectionUtils.isNotEmpty(implClassSet)) {
            return implClassSet;
        }
        return new HashSet<>();
    }

    /**
     * @description: 设置注解对应属性的实现
     * @author: wx
     * @date: 2019/1/11 0011
     * @param fields 属性数组
     * @param annotationClass 注解
     * @return: void
     */
    private static <A extends Annotation>  void  setTheImplementationOfAnnotationCorrespondingProperties(Field[] fields,Class<A> annotationClass) throws ClassNotFoundException {
        for (Field field : fields) {
            //递归具体的查找到底哪个属性上有指定注解
            if (field.getAnnotation(annotationClass) != null) {
                //获取类名
                String newFieldName = field.getType().getName();
                //如果是接口
                if (Class.forName(newFieldName).isInterface()) {
                    //获取该接口对应的实现类
                    Set<String> implClassSet = convertInterfaceToImpl(newFieldName);
                    for (String implClass : implClassSet) {
                        if ("proxy".equals(implClass)){
                            continue;
                        }
                        //如果实现类不存在实例化类容器，则递归调用addAnnotationsInstanceSet
                        if (!IocContainer.getInstanceSet().contains(implClass)) {
                            addAnnotationsInstanceSet(implClass);
                        }
                    }
                } else {
                    addAnnotationsInstanceSet(newFieldName);
                }
            }
        }
    }

}
